﻿/*
 PennyAuctionTracker
 Copyright (C) 2011  Devin Bileck
 
 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software Foundation;
 either version 2 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with this program;
 if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

using System;
using System.Net;
using System.IO;
using System.Collections;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace PennyAuctionTracker
{
	/// <summary>
	/// Implements a penny auction from QuiBids.
	/// </summary>
	public class QuiBids : Auction
	{
		/// <summary>
		/// Stores the session identifier for requests.
		/// </summary>
		private String sessionID;
		
		/// <summary>
		/// Stores the index value of the most recent bid.
		/// </summary>
		private double bidIndex;
		
		/// <summary>
		/// The URI for loading an auction.
		/// </summary>
		private String auctionURI = "http://www.quibids.com/auctions/";
		
		/// <summary>
		/// The URI for retrieving auction details.
		/// </summary>
		private String auctionDetailsURI = "http://www.quibids.com/ajax/u.php";

		/// <summary>
		/// Class constructor.
		/// </summary>
		/// <param name="AuctionID">The auction identifier.</param>
		public QuiBids(String AuctionID)
		{
			this.auctionID = AuctionID;
			this.isValid = Validate();
			this.bidIndex = 0;
		}
		
		/// <summary>
		/// Determines whether the auction parameters are valid.
		/// </summary>
		/// <returns>True if valid; false otherwise.</returns>
		private bool Validate()
		{
			// Attempt to load the auction
			HttpWebResponse response = GetHttpWebResponse(auctionURI + auctionID);
			
			// If the status code of the response is OK
			if ((response != null) && (response.StatusCode == HttpStatusCode.OK))
			{
				// Extract the Set-Cookie header from the response and store the session ID
				String sessionID = "";
				String[] parameters = response.Headers["Set-Cookie"].Split(';');
	        	foreach (String param in parameters)
	        	{
	        		String[] cookie = param.Split('=');
	        		if (cookie[0] == "PHPSESSID")
	        		{
	        			sessionID = cookie[1];
	        			break;
	        		}
	    	    }
	        	this.sessionID = sessionID;
				
	        	// Auction is valid
				return true;
			}
			
			// If the status code of the response is not OK
			else
			{
				// Auction is not valid
				return false;
			}
		}
		
		/// <summary>
		/// Updates the auction.
		/// </summary>
		public override void Update()
		{
			// Get the auction details (in JSON format)
			String json = RequestAuctionDetails();

			// Parse the returned JSON string into a JObject
			JObject jsonObj = JObject.Parse(json);
			
			// If the response contains the auction details
			if ((jsonObj["a"] != null) && (jsonObj["a"][this.auctionID] != null))
			{
				// If the auction details have child tokens
				if (jsonObj["a"][this.auctionID].HasValues)
				{
					// Store the remaining time
					if (jsonObj["a"][this.auctionID]["sl"] != null)
					{
						this.timeRemaining = Double.Parse(jsonObj["a"][this.auctionID]["sl"].ToString());
					}
					else
					{
						this.timeRemaining = 0;
					}
					
					// Store the index value of the most recent bid
					this.bidIndex = Double.Parse(jsonObj["a"][this.auctionID]["v"].ToString());
					
					// Get the bid history
					JArray history = (JArray)jsonObj["a"][this.auctionID]["bh"];
					if (history != null)
					{
						// Check each bid in the history
						foreach (JObject obj in history)
						{
							Bid bid = new Bid();
							bid.Bidder = obj["u"].ToString();
							bid.Price = obj["a"].ToString();
							bid.Type = obj["t"].ToString();
							
							// If this bid hasn't been stored
							if (bidHistory.IndexOf(bid) == -1)
							{
								// Add the bid to the bid history array
								bidHistory.Add(bid);
								
								// Check if the bidder is already in the bidders array and remove them
								int bids = 0;
								foreach (Bidder b in bidders)
								{
									if (b.Name.Equals(obj["u"].ToString()))
									{
										bids = b.Bids;
										bidders.Remove(b);
										break;
									}
								}
								
								// Add the bidder to the bidders array
								Bidder bidder = new Bidder();
								bidder.Name = obj["u"].ToString();
								bidder.Bids = bids + 1;
								bidders.Add(bidder);
							}
						}
					}
				}
				
				// The auction details does not have child tokens
				else
				{
					this.timeRemaining = Double.Parse(jsonObj["a"][this.auctionID].ToString());
				}
			}
			
			// Response doesn't contain auction details
			else
			{
				this.timeRemaining = 0;
			}
		}
		
		/// <summary>
		/// Requests the auction details.
		/// </summary>
		/// <returns>
		/// The auction details will be returned in JSON format. For example:
		///	 {
		///		"a":{
		///			"168589334":{
		///				"bh":[{
		///					"id":9323,
		///					"a":"$91.37",
		///					"u":"carpre",
		///					"t":1
		///				}],
		///				"v":9323,
		///				"p":91.37,
		///				"lb":"carpre",
		///				"av":"77",
		///				"sl":10.3
		///			}
		///		}
		///	 }
		///  Or:
		///	 {
		///		"a":{
		///			"168589334":812
		///		},
		///		"clockAdd":15
		///	 }
		/// </returns>
		private String RequestAuctionDetails()
		{
			// Comile the URI for requesting the auction details
			String aj = GrabId(auctionID) + "|" + this.bidIndex;
			String uri = auctionDetailsURI + "?ids=" + aj + "&cs=" + CValue(aj).Substring(0, 10) + "&lb_id=" + auctionID;
			
			// Get the response
			HttpWebResponse response = GetHttpWebResponse(uri, new string[] {"Cookie:PHPSESSID=" + this.sessionID});
			
			// Return the response content
			try
			{
				String json = "";
				using (StreamReader sr = new StreamReader(response.GetResponseStream()))
				{
				    json = sr.ReadToEnd();
				}
				return json;
			}
			catch
			{
				return "";
			}
		}
		
		#region QuiBids methods
		/*
		 * The following methods are based on the javascript methods from QuiBids for comiling the URI for requesting auction details.
		 */

		public String GrabId(String AuctionId)
		{
			String newId = AuctionId;
			String l = F();
			for (int x = l.Length - 1; x >= 0; x--)
			{
				newId = newId.Replace(x.ToString(), l.Substring(x,1));
			}
			return newId;
		}
		
		private String F()
		{
			String ah = this.sessionID;
			String ai = "";
			for (int x = 0; x < ah.Length; x++)
			{
				char aj = ah[x];
				int ak = (int)aj;
				if ((ak < 65) || (ak > 122) || ((ak > 90) && (ak < 97)))
				{
					continue;
				}
				if (ai.IndexOf(aj) == -1)
				{
					ai += aj;
				}
			}
			if (ai.Length < 50)
			{
				String al = "QwErTyUiOpAsDfGhJkLzXcVbNmqWeRtYuIoPaSdFgHjKlZxCvBnM";
				for (int x = 0; x < al.Length; x++)
				{
					char aj = al[x];
					if (ai.IndexOf(aj) == -1)
					{
						ai += aj;
					}
					if (ai.Length >= 50)
					{
						break;
					}
				}
			}
			return ai;
		}
		
		public String CValue(String t)
		{
		    int T = 7;
		    int R = 12;
		    int O = 17;
		    int N = 22;
		    int B = 5;
		    int A = 9;
		    int z = 14;
		    int y = 20;
		    int o = 4;
		    int m = 11;
		    int l = 16;
		    int j = 23;
		    int V = 6;
		    int U = 10;
		    int S = 15;
		    int P = 21;
		    t = K(t);
		    var D = e(t);
		    long Z = 1732584193;
		    long Y = 4023233417;
		    long X = 2562383102;
		    long W = 271733878;
		    for (int Q = 0; Q < D.Length; Q += 16)
		    {
		        long h = Z;
		        long J = Y;
		        long w = X;
		        long g = W;
		        Z = v(Z, Y, X, W, D[Q + 0], T, 3614090360);
	            W = v(W, Z, Y, X, D[Q + 1], R, 3905402710);
	            X = v(X, W, Z, Y, D[Q + 2], O, 606105819);
	            Y = v(Y, X, W, Z, D[Q + 3], N, 3250441966);
	            Z = v(Z, Y, X, W, D[Q + 4], T, 4118548399);
	            W = v(W, Z, Y, X, D[Q + 5], R, 1200080426);
	            X = v(X, W, Z, Y, D[Q + 6], O, 2821735955);
	            Y = v(Y, X, W, Z, D[Q + 7], N, 4249261313);
	            Z = v(Z, Y, X, W, D[Q + 8], T, 1770035416);
	            W = v(W, Z, Y, X, D[Q + 9], R, 2336552879);
	            X = v(X, W, Z, Y, D[Q + 10], O, 4294925233);
	            Y = v(Y, X, W, Z, D[Q + 11], N, 2304563134);
	            Z = v(Z, Y, X, W, D[Q + 12], T, 1804603682);
	            W = v(W, Z, Y, X, D[Q + 13], R, 4254626195);
	            X = v(X, W, Z, Y, D[Q + 14], O, 2792965006);
	            Y = v(Y, X, W, Z, D[Q + 15], N, 1236535329);
	            Z = f(Z, Y, X, W, D[Q + 1], B, 4129170786);
	            W = f(W, Z, Y, X, D[Q + 6], A, 3225465664);
	            X = f(X, W, Z, Y, D[Q + 11], z, 643717713);
	            Y = f(Y, X, W, Z, D[Q + 0], y, 3921069994);
	            Z = f(Z, Y, X, W, D[Q + 5], B, 3593408605);
	            W = f(W, Z, Y, X, D[Q + 10], A, 38016083);
	            X = f(X, W, Z, Y, D[Q + 15], z, 3634488961);
	            Y = f(Y, X, W, Z, D[Q + 4], y, 3889429448);
	            Z = f(Z, Y, X, W, D[Q + 9], B, 568446438);
	            W = f(W, Z, Y, X, D[Q + 14], A, 3275163606);
	            X = f(X, W, Z, Y, D[Q + 3], z, 4107603335);
	            Y = f(Y, X, W, Z, D[Q + 8], y, 1163531501);
	            Z = f(Z, Y, X, W, D[Q + 13], B, 2850285829);
	            W = f(W, Z, Y, X, D[Q + 2], A, 4243563512);
	            X = f(X, W, Z, Y, D[Q + 7], z, 1735328473);
	            Y = f(Y, X, W, Z, D[Q + 12], y, 2368359562);
	            Z = E(Z, Y, X, W, D[Q + 5], o, 4294588738);
	            W = E(W, Z, Y, X, D[Q + 8], m, 2272392833);
	            X = E(X, W, Z, Y, D[Q + 11], l, 1839030562);
	            Y = E(Y, X, W, Z, D[Q + 14], j, 4259657740);
	            Z = E(Z, Y, X, W, D[Q + 1], o, 2763975236);
	            W = E(W, Z, Y, X, D[Q + 4], m, 1272893353);
	            X = E(X, W, Z, Y, D[Q + 7], l, 4139469664);
	            Y = E(Y, X, W, Z, D[Q + 10], j, 3200236656);
	            Z = E(Z, Y, X, W, D[Q + 13], o, 681279174);
	            W = E(W, Z, Y, X, D[Q + 0], m, 3936430074);
	            X = E(X, W, Z, Y, D[Q + 3], l, 3572445317);
	            Y = E(Y, X, W, Z, D[Q + 6], j, 76029189);
	            Z = E(Z, Y, X, W, D[Q + 9], o, 3654602809);
	            W = E(W, Z, Y, X, D[Q + 12], m, 3873151461);
	            X = E(X, W, Z, Y, D[Q + 15], l, 530742520);
	            Y = E(Y, X, W, Z, D[Q + 2], j, 3299628645);
	            Z = u(Z, Y, X, W, D[Q + 0], V, 4096336452);
	            W = u(W, Z, Y, X, D[Q + 7], U, 1126891415);
	            X = u(X, W, Z, Y, D[Q + 14], S, 2878612391);
	            Y = u(Y, X, W, Z, D[Q + 5], P, 4237533241);
	            Z = u(Z, Y, X, W, D[Q + 12], V, 1700485571);
	            W = u(W, Z, Y, X, D[Q + 3], U, 2399980690);
	            X = u(X, W, Z, Y, D[Q + 10], S, 4293915773);
	            Y = u(Y, X, W, Z, D[Q + 1], P, 2240044497);
	            Z = u(Z, Y, X, W, D[Q + 8], V, 1873313359);
	            W = u(W, Z, Y, X, D[Q + 15], U, 4264355552);
	            X = u(X, W, Z, Y, D[Q + 6], S, 2734768916);
	            Y = u(Y, X, W, Z, D[Q + 13], P, 1309151649);
	            Z = u(Z, Y, X, W, D[Q + 4], V, 4149444226);
	            W = u(W, Z, Y, X, D[Q + 11], U, 3174756917);
	            X = u(X, W, Z, Y, D[Q + 2], S, 718787259);
	            Y = u(Y, X, W, Z, D[Q + 9], P, 3951481745);
	            Z = L(Z, h);
	            Y = L(Y, J);
	            X = L(X, w);
	            W = L(W, g);
		    }
		    String i = C(Z) + C(Y) + C(X) + C(W);
		    return i.ToLower();
		}
		
		private long M(long b, int a)
		{
			return (b << a) | ((long)((ulong)b >> (32 - a)));
	    }
		
	    private long L(long k, long b)
	    {
	        long d = (k & 2147483648);
	        long x = (b & 2147483648);
	        long F = (k & 1073741824);
	        long a = (b & 1073741824);
	        long c = (k & 1073741823) + (b & 1073741823);
	        if ((F & a) > 0)
	        {
	            return (c ^ 2147483648 ^ d ^ x);
	        }
	        if ((F | a) > 0)
	        {
	        	if ((c & 1073741824) > 0)
	            {
	                return (c ^ 3221225472 ^ d ^ x);
	            }
	            else
	            {
	            	return (c ^ 1073741824 ^ d ^ x);
	            }
	        }
	        else
	        {
	            return (c ^ d ^ x);
	        }
	    }
	    
	    private long r(long a, long c, long b)
	    {
	        return (a & c) | ((~a) & b);
	    }
	    
	    private long q(long a, long c, long b)
	    {
	        return (a & b) | (c & (~b));
	    }
	    
	    private long p(long a, long c, long b)
	    {
	        return (a ^ c ^ b);
	    }
	    
	    private long n(long a, long c, long b)
	    {
	        return (c ^ (a | (~b)));
	    }
	    
	    private long v(long G, long F, long ab, long aa, long k, int H, long I)
	    {
	        G = L(G, L(L(r(F, ab, aa), k), I));
	        return L(M(G, H), F);
	    }
	    
	    private long f(long G, long F, long ab, long aa, long k, int H, long I)
	    {
	        G = L(G, L(L(q(F, ab, aa), k), I));
	        return L(M(G, H), F);
	    }
	    
	    private long E(long G, long F, long ab, long aa, long k, int H, long I)
	    {
	        G = L(G, L(L(p(F, ab, aa), k), I));
	        return L(M(G, H), F);
	    }
	    
	    private long u(long G, long F, long ab, long aa, long k, int H, long I)
	    {
	        G = L(G, L(L(n(F, ab, aa), k), I));
	        return L(M(G, H), F);
	    }
	    
	    private long[] e(String k)
	    {
	    	int G;
	        int d = k.Length;
	        int c = d + 8;
	        int b = (c - (c % 64)) / 64;
	        int F = (b + 1) * 16;
	        long[] H = new long[F];
	        int a = 0;
	        int x = 0;
	        while (x < d)
	        {
	            G = (x - (x % 4)) / 4;
	            a = (x % 4) * 8;
	            H[G] = (H[G] | ((int)k[x] << a));
	            x++;
	        }
	        G = (x - (x % 4)) / 4;
	        a = (x % 4) * 8;
	        H[G] = H[G] | (128 << a);
	        H[F - 2] = d << 3;
	        H[F - 1] = (long)((ulong)d >> 29);
	        return H;
	    }
	    
	    private String C(long c)
	    {
	        String b = "";
	        String d = "";
	        for (int a = 0; a <= 3; a++)
	        {
	        	long k = ((long)((ulong)c >> (a * 8))) & 255;
	            d = "0" + k.ToString("X");
	            b = b + d.Substring(d.Length - 2, 2);
	        }
	        return b;
	    }
	    
	    private String K(String b)
	    {
	    	b = System.Text.RegularExpressions.Regex.Replace(b, "/\r\n/g", "\n");
	        String a = "";
	        for (int k = 0; k < b.Length; k++)
	        {
	        	int d = (int)b[k];
	            if (d < 128)
	            {
	            	a += (char)d;
	            }
	            else
	            {
	                if ((d > 127) && (d < 2048))
	                {
	                	a += (char)((d >> 6) | 192);
	                	a += (char)((d & 63) | 128);
	                }
	                else
	                {
	                	a += (char)((d >> 12) | 224);
	                	a += (char)(((d >> 6) & 63) | 128);
	                	a += (char)((d & 63) | 128);
	                }
	            }
	        }
	        return a;
	    }
	    
	    #endregion
	}
}